package org.okwangxing.vsearch.spring;

import java.io.File;
import java.io.FileFilter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.solr.common.util.FileUtils;
import org.apache.solr.core.SolrResourceLoader;
import org.okwangxing.vsearch.resouce.exception.VSearchResouceLoaderException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ExtJarResourceLoader {
	final Logger log = LoggerFactory.getLogger(ExtJarResourceLoader.class);

	private String instanceDir;
	private ClassLoader classLoader;

	public ExtJarResourceLoader() throws MalformedURLException {
		this(null);
	}

	public ExtJarResourceLoader(String instanceDir) {
		if (instanceDir == null) {
			this.instanceDir = "/home/admin/vsearch/lib";
		} else {
			this.instanceDir = normalizeDir(instanceDir);
		}
		log.info("设置外部jar位置 '" + this.instanceDir + "'");
		this.classLoader = createClassLoader(null);

		addToClassLoader(".", null);

	}

	public String getInstanceDir() {
		return this.instanceDir;
	}

	void addToClassLoader(final String baseDir, final FileFilter filter) {
		File base = FileUtils.resolvePath(new File(getInstanceDir()), baseDir);
		this.classLoader = replaceClassLoader((URLClassLoader) classLoader, base, filter);
	}

	void addToClassLoader(final String path) {
		final File file = FileUtils.resolvePath(new File(getInstanceDir()), path);
		if (file.canRead()) {
			this.classLoader = replaceClassLoader((URLClassLoader) classLoader, file.getParentFile(), new FileFilter() {
				public boolean accept(File pathname) {
					return pathname.equals(file);
				}
			});
		} else {
			log.error("Can't find (or read) file to add to classloader: " + file);
		}
	}

	private ClassLoader replaceClassLoader(final URLClassLoader oldLoader, final File base, final FileFilter filter) {
		if (null != base && base.canRead() && base.isDirectory()) {
			File[] files = base.listFiles(filter);

			if (null == files || 0 == files.length)
				return oldLoader;

			URL[] oldElements = oldLoader.getURLs();
			URL[] elements = new URL[oldElements.length + files.length];
			System.arraycopy(oldElements, 0, elements, 0, oldElements.length);

			for (int j = 0; j < files.length; j++) {
				try {
					URL element = files[j].toURI().normalize().toURL();
					log.info("添加 '" + element.toString() + "' to classloader");
					elements[oldElements.length + j] = element;
				} catch (MalformedURLException e) {
					log.error("Can't add element to classloader: " + files[j], e);
				}
			}
			return URLClassLoader.newInstance(elements, oldLoader.getParent());
		}
		return oldLoader;
	}

	public URL getResource(String name) {
		return classLoader.getResource(name);
	}

	ClassLoader createClassLoader(final File libDir) {
		ClassLoader parent = Thread.currentThread().getContextClassLoader();
		return replaceClassLoader(URLClassLoader.newInstance(new URL[0], parent), libDir, null);
	}

	public static String normalizeDir(String path) {
		return (path != null && (!(path.endsWith("/") || path.endsWith("\\")))) ? path + File.separator : path;
	}

	private static Map<String, String> classNameCache = new ConcurrentHashMap<String, String>();

	public Class<?> findClass(String cname, String... subpackages) {
		String c = classNameCache.get(cname);
		if (c != null) {
			try {
				return Class.forName(c, true, classLoader);
			} catch (ClassNotFoundException e) {
				log.error("Unable to load cached class-name :  " + c + " for shortname : " + cname + e);
			}

		}
		Class<?> clazz = null;
		try {
			return Class.forName(cname, true, classLoader);
		} catch (ClassNotFoundException e) {
			String newName = cname;
			for (String subpackage : subpackages) {
				try {
					String name = subpackage + newName;
					log.trace("Trying class name " + name);
					return clazz = Class.forName(name, true, classLoader);
				} catch (ClassNotFoundException e1) {
				}
			}

			throw new VSearchResouceLoaderException("Error loading class '" + cname + "'", e);
		} finally {
			if (clazz != null && clazz.getClassLoader() == SolrResourceLoader.class.getClassLoader() && !cname.equals(clazz.getName()) && (subpackages.length == 0)) {
				classNameCache.put(cname, clazz.getName());
			}
		}
	}

	/**
	 * @return the classLoader
	 */
	public ClassLoader getClassLoader() {
		return classLoader;
	}

}
